#include "api.h"
#include "loc/loc-consts.h"
#include "loc/rva.h"
#include <cassert>
#include <optional>
#include <ranges>
using namespace std;

std::unordered_map<std::string, Spell> Api::spells_{};

void Api::take_taxi(const std::string &dest) {
  uint32 num_nodes = game_->get_num_taxi_nodes();
  for (auto i : views::iota(0u, num_nodes)) {
    uint32 node_id = game_->get_taxi_node_id(i);
    string node_name = game_->get_taxi_node_name_ex(node_id);
    if (node_name == dest) {
      game_->take_taxi(i);
      return;
    }
  }
}

void Api::go(float x, float y, float z) { game_->click(x, y, z, 4); }

uint8 Api::bait_state(uint64 ptr) {
  if (ptr > 0) {
    return r1(ptr + 0x14C) & 0xF;
  }
  return 0;
}

std::vector<std::shared_ptr<Ground>> Api::grounds() {
  grounds_.clear();
  auto core_tmp_index = r4(base_ + RVA_CORE_ARR_TMP_INDEX_BASE);
  uintptr core_arr = r8(game_->core_base() + core_tmp_index * 8);
  assert(core_arr != 0);
  uintptr arr = r8(r8(core_arr + get_object_check2) + 8);
  uint32 size = r4(r8(core_arr + get_object_check2) + 0x10);
  for (auto i : views::iota(0u, size)) {
    uintptr p1 = r8(arr + i * 8);
    if (p1 > 0) {
      uintptr p2 = r8(p1 + 0x28);
      if (p2 > 0 && (r2(p2 + 8) & 0xFFF) == 0x808) {
        grounds_.push_back(make_shared<Ground>(p2));
      }
    }
  }
  // 根据距离排序
  sort(grounds_.begin(), grounds_.end(),
       [&](const shared_ptr<Ground> &a, const shared_ptr<Ground> &b) {
         return player()->dist_ground(a) < player()->dist_ground(b);
       });
  return grounds_;
}

std::vector<std::shared_ptr<Item>> Api::equip_items() {
  equip_items_.clear();
  for (auto i : views::iota(0u, 0x23u)) {
    auto item_ptr = get_equip_item_ptr(i);
    shared_ptr<Item> equip_item{nullptr};
    if (item_ptr > 0) {
      equip_item = make_shared<Item>(base_, item_ptr, item_cache_);
    }
    equip_items_.push_back(equip_item);
  }
  return equip_items_;
}

// slots: [0-0x23)
uint64 Api::get_equip_item_ptr(uint32 slots) const {
  auto item_id_arr = player()->ptr() + backpack_item_id_arr_offset;
  if (slots >= 0x23) {
    return 0;
  }
  auto item_id_ptr = r8(item_id_arr + 8) + slots * 0x10;
  auto id1 = r8(item_id_ptr);
  auto id2 = r8(item_id_ptr + 8);
  auto item_ptr = game_->get_object(id1, id2);
  return item_ptr;
}

std::vector<std::shared_ptr<Item>> Api::container_items() {
  container_items_.clear();
  for (auto i : views::iota(0u, player()->backpack_num_slots())) {

    auto item_ptr = get_container_item_ptr(0, i);
    if (item_ptr > 0) {
      container_items_.push_back(
          make_shared<Item>(base_, item_ptr, item_cache_));
    }
  }
  for (auto i : views::iota(1u, 5u)) {
    auto container_opt = get_container(i - 1);
    if (container_opt.has_value()) {
      auto container = container_opt.value();
      for (auto ii : views::iota(0u, container.num_slots())) {
        auto item_ptr = get_container_item_ptr(i, ii);
        if (item_ptr > 0) {
          container_items_.push_back(
              make_shared<Item>(base_, item_ptr, item_cache_));
        }
      }
    }
  }
  return container_items_;
}

// 获取背包剩余空格数
uint32 Api::get_num_free_slots() const {
  uint32 ret{0};
  // // 背包1-4剩余格子数
  for (auto i : views::iota(0u, 5u)) {
    for (auto ii : views::iota(0u, get_container_num_slots(i))) {
      auto item_ptr = get_container_item_ptr(i, ii);
      if (item_ptr == 0)
        ret++;
    }
  }
  return ret;
}

// 获取背包格子数; container_index 主背包 0, 背包 1-4
uint32 Api::get_container_num_slots(uint32 container_index) const {
  if (container_index < 0 || container_index > 4) {
    return 0;
  }
  if (container_index == 0) {
    return player()->backpack_num_slots();
  }
  auto container_opt = get_container(container_index - 1);
  if (!container_opt.has_value()) {
    return 0;
  }
  return container_opt.value().num_slots();
}

optional<Item> Api::get_container_item(uint32 container_index,
                                       uint32 slots) const {
  auto item_ptr = get_container_item_ptr(container_index, slots);
  if (item_ptr > 0) {
    return make_optional<Item>(base_, item_ptr, item_cache_);
  }
  return nullopt;
}

optional<Item> Api::get_container_item_by_id(uint64 id1, uint64 id2) const {
  auto item_ptr = game_->get_object(id1, id2);
  if (item_ptr > 0) {
    return make_optional<Item>(base_, item_ptr, item_cache_);
  }
  return nullopt;
}

// container_index: 0 主背包; 1-4 背包
// slots: 0-container_size
// 如果没有物品, 返回 0
uint64 Api::get_container_item_ptr(uint32 container_index, uint32 slots) const {
  if (container_index > 4) {
    return 0;
  }
  uint64 item_id_arr{0};
  uint32 container_size{0};
  if (container_index == 0) {
    item_id_arr = player()->ptr() + backpack_item_id_arr_offset;
    container_size = player()->backpack_num_slots();
    if (slots >= container_size) {
      return 0;
    }
    slots += 0x23;
  }
  if (container_index > 0) {
    auto container_opt = get_container(container_index - 1);
    if (!container_opt.has_value()) {
      return 0;
    }
    auto container = container_opt.value();
    item_id_arr = container.ptr() + container_item_id_arr_offset;
    container_size = container.num_slots();
    if (slots >= container_size) {
      return 0;
    }
  }

  auto item_id_ptr = r8(item_id_arr + 8) + slots * 0x10;
  auto id1 = r8(item_id_ptr);
  auto id2 = r8(item_id_ptr + 8);
  auto item_ptr = game_->get_object(id1, id2);
  return item_ptr;
}

// 获取背包 index:[0-3]
optional<Container> Api::get_container(uint32 index) const {
  auto base = (base_ + RVA_CONTAINER_ID_BASE) + index * 0x10;
  auto id1 = r8(base);
  auto id2 = r8(base + 8);
  auto container_ptr = game_->get_object(id1, id2);
  if (container_ptr > 0) {
    return make_optional<Container>(base_, container_ptr, item_cache_);
  }
  return nullopt;
}

void Api::load_item_type_cache() { item_cache_->load_from_disk(); }

void Api::save_item_type_cache() { item_cache_->save_to_disk(); }

std::unordered_map<std::string, Spell> Api::spells() {
  spells_.clear();
  uint64 base = base_ + RVA_SPELL_ID_BASE;
  uint64 arr = r8(base + 0x30);
  uint32 size = r4(base + 0x288);
  for (auto i : views::iota(0u, size)) {
    uint64 spell_id_ptr = r8(arr + i * 8);
    if (spell_id_ptr == 0) {
      continue;
    }

    Spell spell;
    spell.spell_id = r4(spell_id_ptr + 4);
    spell.name = game_->get_spell_name_ex(spell.spell_id);
    spells_.insert({spell.name, spell});
  }
  return spells_;
}

optional<Buff> Api::get_buff(const string &name) const {
  auto unit_ptr = player()->ptr();
  auto buffs = this->buffs();
  if (buffs.contains(name)) {
    return buffs[name];
  }
  return nullopt;
}

optional<Buff> Api::unit_get_buff(const uint64 unit_ptr,
                                  const string &name) const {
  auto buffs = this->buffs(unit_ptr);
  if (buffs.contains(name)) {
    return buffs[name];
  }
  return nullopt;
}

bool Api::has_buff(const std::string &buff_name) const {
  auto buffs = this->buffs();
  return buffs.contains(buff_name);
}

bool Api::unit_has_buff(uint64 unit_ptr, const std::string &buff_name) const {
  if (unit_ptr > 0) {
    auto buffs = this->buffs(unit_ptr);
    return buffs.contains(buff_name);
  }
  return false;
}

unordered_map<string, Buff> Api::buffs(uint64 unit_ptr) const {
  std::unordered_map<std::string, Buff> buffs;
  if (unit_ptr == 0) {
    unit_ptr = player()->ptr();
  }
  uint64 arr = r8(unit_ptr + buff_arr_offset);
  uint64 size = r4(unit_ptr + buff_arr_size_offset);
  for (auto i : views::iota(0u, size)) {
    // i = (i * 3) << 6;
    i = i * 0xD0;
    auto buff_id = r4(arr + i + buff_offset);
    if (buff_id > 0) {
      Buff buff;
      buff.buff_id = buff_id;
      // buff.name = game_->get_spell_name(buff_id);
      buff.name = game_->get_spell_name_ex(buff_id);
      buff.duration = r4(arr + i + 0xB8);
      buff.expiration = r4(arr + i + 0xBC);
      buffs.insert({buff.name, buff});
    }
  }
  return buffs;
}

// 根据 unit 名称获取一个 unit 实例(返回的是最近的).
std::shared_ptr<Unit> Api::get_unit(const std::string &unit_name) {
  for (auto unit : units()) {
    if (unit->name() == unit_name) {
      return unit;
    }
  }
  return nullptr;
}

optional<Unit> Api::get_unit(uint64 id1, uint64 id2) const {
  auto unit_ptr = game_->get_object(id1, id2);
  if (unit_ptr == 0) {
    return nullopt;
  }
  return make_optional(Unit(unit_ptr));
}

vector<shared_ptr<Unit>> Api::units() {
  units_.clear();
  auto core_tmp_index = r4(base_ + RVA_CORE_ARR_TMP_INDEX_BASE);
  uintptr core_arr = r8(game_->core_base() + core_tmp_index * 8);
  assert(core_arr != 0);
  uintptr arr = r8(r8(core_arr + get_object_check2) + 8);
  uint32 size = r4(r8(core_arr + get_object_check2) + 0x10);
  for (auto i : views::iota(0u, size)) {
    uintptr p1 = r8(arr + i * 8);
    if (p1 > 0) {
      uintptr p2 = r8(p1 + 0x28);
      if (p2 > 0 && (r2(p2 + 8) & 0xFFF) == 0x505 /*r1(p2 + 8) == 5*/) {
        units_.push_back(make_shared<Unit>(p2));
      }
    }
  }
  // 根据距离排序
  sort(units_.begin(), units_.end(),
       [&](const shared_ptr<Unit> &a, const shared_ptr<Unit> &b) {
         return player()->dist_unit(a->role()) < player()->dist_unit(b->role());
       });
  return units_;
}

std::shared_ptr<Player> Api::player() const {
  if (player_ == nullptr) {
    auto player_id_base = r8(base_ + RVA_PLAYER_ID_BASE) + 0x10;
    auto id1 = r8(player_id_base + 0x0);
    auto id2 = r8(player_id_base + 0x8);
    auto index = game_->get_object_index(id1, id2);
    auto ptr = game_->get_object(index);
    assert(ptr != 0);
    player_ = std::make_shared<Player>(base_, ptr);
  }
  return player_;
}

float Api::dist(float x1, float y1, float z1, float x2, float y2, float z2) {
  return sqrt(pow(x2 - x1, 2) + pow(y2 - y1, 2) + pow(z2 - z1, 2));
}

void Api::set_time(uint32 time) { time_ = time; }

uint32 Api::get_time() { return time_; }

Api::Api(uint64 base, shared_ptr<Game> game) : base_{base}, game_{game} {
  item_cache_ = make_shared<ItemCache>(game);
}

Api::~Api() {}

std::shared_ptr<Player> Api::player_{nullptr};
